<?php
/**
 * Created by Zhy
 * User: Zhy
 * Date: 2020/11/3
 * Time: 14:11
 */

namespace pay;


class Wxservicer
{
    // 下单请求地址
    public $url_V2 = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; // V2版本
    public $url_V3 = 'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app'; // V3版本

    // 退款请求地址
    public $tui_url_V2 = 'https://api.mch.weixin.qq.com/secapi/pay/refund'; // V2版本
    public $tui_url_V3 = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds'; // V3版本

    public $sp_appid; // 服务商在开放平台申请的应用appid

    public $sp_mchid; // 服务商户号，由微信支付生成并下发

    public $sub_appid; // 子商户在开放平台申请的应用appid（非必须）

    public $sub_mchid; // 子商户的商户号，由微信支付生成并下发

    public $notify_url; // 回调地址

    public $key_V2; // 支付KEY V2版本

    public $key_V3; // 支付KEY V3版本
    
    public function __construct($config = [])
    {
        $this->sp_appid = $config['sp_appid'];
        $this->sp_mchid = $config['sp_mchid'];
        $this->sub_appid = $config['sub_appid'];
        $this->sub_mchid = $config['sub_mchid'];
        $this->notify_url = $config['notify_url'];
        $this->key_V2 = $config['key_V2'];
        $this->key_V3 = $config['key_V3'];
    }

    //**********************************   V2版本 start  **************************************//

    /*
     * APP下单---服务商模式  V2版本
     * @param $total_fee       金额
     * @param $out_trade_no    订单号
     * @param $body            描述
     * @return mixed
     */
    public function WeChatPayReadyServicerV2($total_fee, $out_trade_no, $body){
        // 获取配置项
        $config = [
            'appid'            => $this->sp_appid,
            'mch_id'           => $this->sp_mchid,
            'sub_mch_id'       => $this->sub_mchid,
//            'sub_appid'        => $this->sub_appid, //
            'nonce_str'        => 'test',
            'body'             => $body,
            'out_trade_no'     => $out_trade_no,
            'total_fee'        => $total_fee * 100,
//            'total_fee'        => 1,
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'notify_url'       => $this->notify_url,
            'trade_type'       => 'APP'
        ];

        try{
            $data = $config;
            // 生成签名
            $sign = $this->makeSign($config);
            $data['sign'] = $sign;
            $xml=$this->toXml($data);
            $url = $this->url_V2;//接收xml数据的文件
            $header[] = "Content-type: text/xml";//定义content-type为xml,注意是数组
            $ch = curl_init ($url);
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
            $response = curl_exec($ch);
            if(curl_errno($ch)){
                print curl_error($ch);
            }
            curl_close($ch);

            $result = $this->toArray($response);
//            print_r($result);die;
            $result['sign'] = $sign;
            $result['nonce_str'] = 'test';
            $result2 = $this->getOrder($result['prepay_id']);
            return ['status' => 200, 'message' => $result2];
        }catch (\Exception $e){
            return ['status' => 0, 'message' => $e->getMessage()];
        }
    }
    
    /*
     * 微信退款--- V2版本
     * @param $total_fee       订单金额
     * @param $refund_fee      退款金额
     * @param $out_trade_no    付款时的订单号
     * @param $out_refund_no   退款订单号
     * @return array
     */
    public function weixiReturn($total_fee, $refund_fee, $out_trade_no, $out_refund_no){
        //请求数据
        $config_data = [
            'appid' => $this->sp_appid,
            'mch_id' => $this->sp_mchid,
            'sub_mch_id' => $this->sub_mchid,
            'nonce_str' => $this->getRandChar(32),
            'out_trade_no' => $out_trade_no, //支付订单号
            'out_refund_no' => $out_refund_no, //退款单号
            'total_fee' => $total_fee * 100, //订单金额
//            'total_fee' => 1, //订单金额
            'refund_fee' => $refund_fee * 100, //退款金额
//            'refund_fee' => 1, //退款金额
        ];
        $config_data['sign'] = $this->makeSign($config_data);
        $data = $this->toXml($config_data);
        $responseArr = $this->toArray($this->httpRequest($this->tui_url_V2, $data));
//        return $res;
        if(!is_array($responseArr) || count($responseArr)<=0){
            $return['msg'] = '网络请求繁忙';
            $return['status'] = 0;
        }else if($responseArr['return_code'] != 'SUCCESS'){
            $return['msg'] = $responseArr['return_msg'];
            $return['status'] = 0;
        }else if($responseArr['result_code'] != 'SUCCESS'){
            $return['msg'] = $responseArr['err_code_des'];
            $return['status'] = 0;
        }else{
            //二次签名
            $return['msg'] = $responseArr;
//			直接返回
            $return['status'] = 200;
        }
        return $return;
    }
    
    /*
     * 提现
     * @param $order_num           订单号
     * @param $withdraw_openid     用户唯一标识
     * @param $real_name           真实姓名
     * @param $price               提现金额
     * @return \SimpleXMLElement
     */
    public function wxTiXian($order_num, $withdraw_openid, $price, $real_name = ''){
        $data = [];
        // 商户号的appid
        $data['mch_appid'] = $this->appId;
        // 商户号
        $data['mchid'] = $this->MCHID;
        // 随机数
        $data['nonce_str'] = $this->getRandChar(32);
        // 订单号
        $data['partner_trade_no'] = $order_num;
        // 用户的唯一标识
        $data['openid'] = $withdraw_openid;
        // 是否校验真实姓名  FORCE_CHECK:强制校验   NO_CHECK:不校验
        if($real_name == ''){
            $data['check_name'] = 'NO_CHECK';
        }else{
            $data['check_name'] = 'FORCE_CHECK';
        }
        // 真实姓名
        $data['re_user_name'] = $real_name;
        // 金额
        $data['amount'] = $price * 100;
        // 备注
        $data['desc'] = '用户提现';
        // ip地址
        $data['spbill_create_ip'] = request()->ip();
        // 签名
        $data['sign'] = $this->makeSign($data);
        // 将数据转换成xml格式
        $xml = $this->toXml($data);
        // 将生成的xml数据进行请求
        $response = $this->httpRequest($this->url, $xml);
        $rdata = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA);
        $return_code = (string)$rdata->return_code;
        $result_code = (string)$rdata->result_code;
        $return_code = trim(strtoupper($return_code));
        $result_code = trim(strtoupper($result_code));
        if($return_code == 'SUCCESS' && $result_code == 'SUCCESS'){
            $success = ['status' => 200, 'msg' => ''];
        }else{
            $success = ['status' => 0, 'msg' => (string)$rdata->return_msg];
        }
        return $success;
    }
    
    /*
     * 发送请求
     * @param  string $url      请求的url地址
     * @param  array  $postData 请求的数据
     */
    public function httpRequest($url, $postData = [])
    {
        $header[] = "Content-type: text/xml";//定义content-type为xml,注意是数组
        // 初始化curl
        $ch = curl_init();
        // 设置请求的url地址
        curl_setopt($ch, CURLOPT_URL, $url);
        // 将curl_exec()获取的信息以字符串返回，而不是直接输出。
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // 发送post请求
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        //curl注意事项，如果发送的请求是https，必须要禁止服务器端校检SSL证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'pem');
        curl_setopt($ch,CURLOPT_SSLCERT,ROOT_PATH.'/public/apiclient_cert.pem');
        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'pem');
        curl_setopt($ch,CURLOPT_SSLKEY,ROOT_PATH.'/public/apiclient_key.pem');
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        
        // 执行curl
        $result = curl_exec($ch);
        if(curl_errno($ch)){
            print curl_error($ch);
        }
        // 关闭curl
        curl_close($ch);
        
        return $result;
    }

    /*
     * 输出xml字符
     * @throws WxPayException
     **/
    public function toXml($data){
        //var_dump($data);
        if(!is_array($data) || count($data) <= 0){
            //throw new WxPayException("数组数据异常！");
        }
        $xml = "<xml>";
        foreach ($data as $key=>$val){
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    
    /*
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array       转换得到的数组
     */
    public function toArray($xml){
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $result;
    }
    
    /*
     * 获取指定长度的随机字符串
     * @param $length
     * @return null|string
     */
    function getRandChar($length){
        $str = null;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($strPol)-1;
        for($i=0;$i<$length;$i++){
            $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }
        return $str;
    }

    //执行第二次签名，才能返回给客户端使用
    public function getOrder($prepayId){
        $data["appid"] = $this->sub_appid; // 此appid必须与前端配置的appid一致
        $data["noncestr"] = $this->getRandChar(32);
        $data["package"] = "Sign=WXPay";
        $data["partnerid"] = $this->sub_mchid;
        $data["prepayid"] = $prepayId;
        $data["timestamp"] = time();
        $s = $this->makeSign($data, false);
        $data["sign"] = $s;

        return $data;
    }

    /*
     * 生成签名
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($data){
        // 去空
        $data = array_filter($data);
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string_a = http_build_query($data);
        $string_a = urldecode($string_a);
        //签名步骤二：在string后加入KEY
        $string_sign_temp = $string_a."&key=".$this->key_V2;
        //签名步骤三：MD5加密
        $sign = md5($string_sign_temp);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($sign);
        return $result;
    }

    //**********************************   V2版本 end  **************************************//


    //**********************************   V3版本 start  **************************************//
    // TODO: 编写V3版本代码





    //**********************************   V3版本 end  **************************************//
}